package com.fortune.cat.manager;

import groovy.lang.GroovyClassLoader;
import org.apache.commons.lang3.RandomUtils;
import org.codehaus.groovy.classgen.ClassGeneratorException;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import static com.fortune.cat.enums.SupportedExternalCompilationEnums.GROOVY;
import static com.fortune.cat.enums.SupportedExternalCompilationEnums.JAVA;

/**
 * @author zhaozeyang
 * @date 2019/4/22 16:44
 * @description 外部代码编译管理
 */
public class ClassManager extends ClassLoader implements ApplicationContextAware {


    private final static GroovyClassLoader loader = new GroovyClassLoader();

    private final static String BASE_JAVA_PACKAGE = "com.fortune.cat.plugins.";

    private static ConfigurableApplicationContext configurableApplicationContext;

    /**
     * 获取java编译器
     */
    private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

    /**
     * 自定义FileManager
     */
    private final FileManager manager = new FileManager(this.compiler);

    public String loadExternalBean(URL url, String externalName) throws IOException {
        if (externalName.endsWith(GROOVY.getValue())) {
            return loadGroovy(url, externalName);
        }
        if (externalName.endsWith(JAVA.getValue())) {
            return loadJava(url, externalName);
        }
        return null;

    }

    public static void main(String[] args) throws IOException {
//        URL url = new URL("https://xesapp-xesapi-test.oss-cn-beijing.aliyuncs.com/java/TokenVerifyPlugin.java");
//        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//        conn.setConnectTimeout(10 * 1000);
//        conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
//        InputStream inputStream = conn.getInputStream();

        InputStream inputStream = new FileInputStream(new File("/Users/zhaozeyang/project/hwl/zhaozeyang/fortune-cat/fortune-cat-gateway/src/main/java/com/fortune/cat/plugins/TokenVerifyPlugin.java"));
        StringBuffer out = new StringBuffer();
        byte[] b = new byte[4096];
        for (int n; (n = inputStream.read(b)) != -1; ) {
            out.append(new String(b, 0, n));
        }
        ClassManager classManager = new ClassManager();
        classManager.compileClass("TokenVerifyPlugin", out.toString());
    }

    private String loadJava(URL url, String externalName) throws IOException {
        InputStream inputStream = null;
        try {
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(10 * 1000);
            conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
            inputStream = conn.getInputStream();
            StringBuffer out = new StringBuffer();
            byte[] b = new byte[4096];
            for (int n; (n = inputStream.read(b)) != -1; ) {
                out.append(new String(b, 0, n));
            }
            externalName = externalName.replace(".java", "");
            return registerBean(compileClass(BASE_JAVA_PACKAGE.concat(externalName), out.toString()));
        } finally {
            if (Objects.nonNull(inputStream)) {
                inputStream.close();
            }
        }
    }

    /**
     * load groovy ，compile ，
     *
     * @return
     */

    public String loadGroovy(URL url, String fileName) throws IOException {
        InputStream inputStream = null;
        //https://xesapp-xesapi-test.oss-cn-beijing.aliyuncs.com/groovy/TestGroovyFilter.groovy
        try {
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(10 * 1000);
            conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
            inputStream = conn.getInputStream();
            Class clazz = loader.parseClass(inputStream, fileName);
            return registerBean(clazz);
        } finally {
            if (Objects.nonNull(inputStream)) {
                inputStream.close();
            }
        }
    }


    public Class<?> compileClass(String name, String code) {

        List<FileManager.Source> list = new ArrayList<>();
        //输入
        list.add(new FileManager.Source(name, JavaFileObject.Kind.SOURCE, code));
        StringWriter stringWriter = new StringWriter();
        //生成并执行编译任务
        ArrayList<String> ops = new ArrayList<>();
        //注: TokenVerifyPlugin.java使用了未经检查或不安全的操作。
        //注: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。
        ops.add("-Xlint:unchecked");
        ops.add("-source");
        ops.add("1.8");
        this.compiler.getTask(stringWriter, this.manager, null, ops, null, list).call();
        //输出
        FileManager.Output mc = this.manager.getOutput(name);
        if (mc != null) {
            //转换成byte[]
            byte[] array = mc.toByteArray();
            //需要继承ClassLoader
            return defineClass(name, array, 0, array.length);
        }
        throw new ClassGeneratorException("动态编译失败");
    }

    private String registerBean(Class clazz) {
        //获取beanFactory
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext
                .getBeanFactory();
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
                .genericBeanDefinition(clazz);
        String beanName = getBeanName(clazz.getSimpleName());
        //注册 bean到spring 容器
        defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());
        return beanName;
    }

    public void removeBean(String targetBeanName) {
        //获取beanFactory
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext
                .getBeanFactory();
        boolean containsBean = defaultListableBeanFactory.containsBean(targetBeanName);
        if (containsBean) {
            //移除bean的定义和实例
            defaultListableBeanFactory.removeBeanDefinition(targetBeanName);
        }
    }


    /**
     * 生成bean name，规则为首字母小写驼峰
     *
     * @param name
     * @return
     */
    private static String getBeanName(String name) {
        return (new StringBuilder()).append(Character.toLowerCase(name.charAt(0))).append(name.substring(1)).toString().concat(String.valueOf(RandomUtils.nextLong()));
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
    }
}
